/***************************************************************************
 * This program is free software; you can redistribute it and/or           *
 * modify it under the terms of the GNU General Public License             *
 * as published by the Free Software Foundation; either version 2          *
 * of the License, or (at your option) any later version.                  *
 *                                                                         *
 * This program is distributed in the hope that it will be useful,         *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with this program; if not, write to the Free Software Foundation, *
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.         *
 *                                                                         *
 *   Copyright (C) 2007,2008 by Ivan Vucica                                *
 *   ivucica@gmail.com                                                     *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream> // removeme!!
#include <sstream>
#include "logointerpreter.h"
void onlogo_printf (const char* txt, ...) ;

Token LogoInterpreter::GetArg(consttokeniterator_t &it, LogoContext &global, LogoContext &local) {
	std::stringstream stst;
	std::vector<Token> tv;

	if (it->type == Token::TT_EOF) {
		throw std::string("Needs more arguments.");
	}
	if (it->type == Token::TT_NL) {
		throw std::string("Needs more arguments.") ;
	}

	if ((it+1)->type != Token::TT_OPERATOR) {
		if (it->type == Token::TT_OPERATOR) {
			if (it->op == Token::OP_MINUS) {
				it++;
				std::stringstream s;
				s << (it->Evaluate(global, local).number * -1);
				return Token(s.str());
			}
		}
		return *it;
	}

	tv.insert(tv.end(), *it);
	for (consttokeniterator_t itt = it; (itt+1)->type == Token::TT_OPERATOR; itt+=2) {
		tv.insert(tv.end(), *(itt+1));
		tv.insert(tv.end(), *(itt+2));
	}

	for (int i = 0; i < tv.size()-2 && tv[i+1].type == Token::TT_OPERATOR ; ) {
		stst.str("");
		switch (tv[i+1].op) {
			case Token::OP_PLUS:
			case Token::OP_MINUS:
			case Token::OP_MULTIPLY:
			case Token::OP_DIVIDE:

				i+=2;
				break;
			case Token::OP_EQUALS:
				if (tv[i].Evaluate(global, local)==tv[i+2].Evaluate(global, local))
					tv[i] = Token("\"TRUE");
				else
					tv[i] = Token("\"FALSE");

				tv.erase(tv.begin()+i+1);
				tv.erase(tv.begin()+i+1);

				break;
			default:{
				std::stringstream ss;
				ss << "While parsing expression, unexpected operator token type encountered (op " << (tv[i+1]).op << ", token is op: " << ((tv[i+1]).type == Token::TT_OPERATOR ? "yes" : "no") << ")";
				//std::cout << "khaotic call to abort(): " << ss.str() << std::endl;
				//abort();
				// ERROR HAPPENS because of incorrect parsing of (360/:C) as an expression
				throw ss.str();
			}
		}
	}



	for (int i = 0; i < tv.size()-2 && tv[i+1].type == Token::TT_OPERATOR ; ) {
		stst.str("");

//		std::cout << "Evaluating: " << tv[i].Describe() << " " << tv[i+1].Describe() << " " << tv[i+2].Describe() << std::endl;

		switch ((tv[i+1]).op) {
			case Token::OP_PLUS:
			case Token::OP_MINUS:
				i+=2;
				break;
			case Token::OP_MULTIPLY:
			case Token::OP_DIVIDE:

//				printf("Evaluating ...\n");
				if (tv[i].Evaluate(global, local).type != Token::TT_NUMBER && tv[i].Evaluate(global, local).type != Token::TT_EXPRESSION) {
					throw std::string("Left of ") + (tv[i+1]).Describe() + " should be a number or an expression, but it's evaluated into '" + tv[i].Evaluate(global, local).Describe() + "' (before evaluation: '" + tv[i].Describe() + "')";
				}
				if ((tv[i+2]).Evaluate(global, local).type != Token::TT_NUMBER && (tv[i+2]).Evaluate(global, local).type != Token::TT_EXPRESSION) {
					if ((tv[i+2]).Evaluate(global, local).type == Token::TT_OPERATOR) {
						consttokeniterator_t cit = (consttokeniterator_t)(tv.begin()+i);

						tv[i] = GetArg(cit, global, local);
						tv.erase(tv.begin()+i+1);
					} else
						throw std::string("Right of ") + (tv[i+1]).Describe() + " should be a number or an expression, but it's evaluated into '" + (tv[i+2]).Evaluate(global, local).Describe() + "' (before evaluation: '" + (tv[i+2]).Describe() + "')";
				}

				if ((tv[i+1]).op == Token::OP_MULTIPLY) {
					//printf("Evaluating %s * %s\n", intit->Evaluate(global, local).Describe().c_str(), (intit+2)->Evaluate(global, local).Describe().c_str());
					stst << (tv[i].Evaluate(global, local).number * (tv[i+2]).Evaluate(global, local).number);
					//std::cout << intit->Evaluate(local).number << "*" << (intit+2)->Evaluate(local).number << "=" << stst.str() << std::endl;

				}
				else {
					//printf("Evaluating %s / %s\n", intit->Evaluate(global, local).Describe().c_str(), (intit+2)->Evaluate(global, local).Describe().c_str());
					stst << (tv[i].Evaluate(global, local).number / (tv[i+2]).Evaluate(global, local).number);
					//std::cout << intit->Evaluate(local).number << "/" << (intit+2)->Evaluate(local).number << "=" << stst.str() << std::endl;
				}

				//printf("Ack\n");
				tv[i] = Token(stst.str());

				tv.erase(tv.begin()+i+1);
				tv.erase(tv.begin()+i+1);

				/*
				for (std::vector<Token>::iterator intit2 = tv.begin(); intit2 != tv.end(); intit2++) {
					printf("--> %s\n", intit2->Describe().c_str());
				}
				*/


				break;
			default:
				throw std::string("While parsing expression, unexpected operator token type  encountered");
		}
	}


	

	for (int i = 0; i < tv.size()-2 && tv[i+1].type == Token::TT_OPERATOR ; ) {
		stst.str("");
//		std::cout << "Evaluating: " << tv[i].Describe() << " " << tv[i+1].Describe() << " " << tv[i+2].Describe() << std::endl;
		switch ((tv[i+1]).op) {
			case Token::OP_PLUS:
			case Token::OP_MINUS:


				if (tv[i].Evaluate(global, local).type != Token::TT_NUMBER && tv[i].Evaluate(global, local).type != Token::TT_EXPRESSION) {
					if ((tv[i+1]).op == Token::OP_MINUS) { // prefixal minus
						return tv[0];
					}
					throw std::string("Left of ") + (tv[i+1]).Describe() + " should be a number or an expression, but it's evaluated into '" + tv[i].Evaluate(global, local).Describe() + "' (before evaluation: '" + tv[i].Describe() + "')";
				}
				if ((tv[i+2]).Evaluate(global, local).type != Token::TT_NUMBER && (tv[i+2]).Evaluate(global, local).type != Token::TT_EXPRESSION) {
					if ((tv[i+2]).Evaluate(global, local).type == Token::TT_OPERATOR) {
						consttokeniterator_t cit = (consttokeniterator_t)(tv.begin()+i);

						tv[i] = GetArg(cit, global, local);
						tv.erase(tv.begin()+i+1);
					} else
						throw std::string("Right of ") + (tv[i+1]).Describe() + " should be a number or an expression, but it's evaluated into '" + (tv[i+2]).Evaluate(global, local).Describe() + "' (before evaluation: '" + (tv[i+2]).Describe() + "')";
				}

				if ((tv[i+1]).op == Token::OP_PLUS) {
					stst << (tv[i].Evaluate(global, local).number + (tv[i+2]).Evaluate(global, local).number);
					//std::cout << tv[i].Evaluate(local).number << "+" << (tv[i+2]).Evaluate(local).number << "=" << stst.str() << std::endl;

				}
				else {
					stst << (tv[i].Evaluate(global, local).number - (tv[i+2]).Evaluate(global, local).number);
					//std::cout << tv[i].Evaluate(local).number << "-" << (tv[i+2]).Evaluate(local).number << "=" << stst.str() << std::endl;
				}

				tv[i] = Token(stst.str());

				tv.erase(tv.begin()+i+1);
				tv.erase(tv.begin()+i+1);

				break;
			default:
				throw std::string("While parsing expression, unexpected operator token type  encountered");
		}
	}





		while ((it+1)->type == Token::TT_OPERATOR) {
			it+=2;
		}


	if (tv.size()!=1)
		throw std::string("Expression was not successfully calculated");
	else
		return tv[0];
}

Token LogoInterpreter::Execute(LogoContext &global, LogoContext &local) {

	//printf("STARTING EXECUTION OF: ----------------\n%s\n------------------\n\n", this->source.c_str());


	for (consttokeniterator_t it = sourcetokens.begin(); it != sourcetokens.end(); it++) {



		switch (it->type) {
			case Token::TT_PROCEDURE: {
				std::string proc = it->text;

				if (global.procedures.find(proc) == global.procedures.end()) {
					throw(std::string("No such procedure ") + proc);
				}

				it++;


				std::vector<Token>args;
				int argc = global.procedures[ proc ].argc;

				for (int i = 0; i < argc; i++, it++) {
					args.insert(args.end(), GetArg(it, global, local));
					/*
					std::cout << GetArg(it, global, local).Describe() << std::endl;
					*/
				}
				it--;


				if (global.procedures [proc].primitive) {
					Token a;
					if ((a = global.procedures [proc].prim (args, global, local)).type != Token::TT_UNDEFINED)
						return a;
				} else {
					LogoInterpreter li (global.procedures[proc].proctext);
					LogoContext lc;

					int i=0;
					for (std::vector<std::string>::iterator it = global.procedures[proc].procparams.begin(); it!=global.procedures[proc].procparams.end(); it++,i++) {
//						std::cout << "Transforming " << args[i].Describe() << " into " << args[i].Evaluate(global, local).Describe() << std::endl;
						lc.variables[*it] = args[i].Evaluate(global, local);
					}

					li.Execute (global, lc); // global context is the global context, local context is a new context filled with arguments
				}



				break;
			}
			case Token::TT_EXPRESSION: {
				LogoSource le(it->text.substr(1, it->text.size()-2));
				if (le.sourcetokens.begin()->type != Token::TT_PROCEDURE)
					throw(std::string("Unexpected token in expression which should be a parenthesis-delimited procedure call, but is instead ") + le.sourcetokens.begin()->Describe());

				std::string proc = le.sourcetokens.begin()->text;

				if (global.procedures.find(proc) == global.procedures.end()) {
					throw(std::string("No such procedure ") + proc);
				}

				std::vector<Token>args(le.sourcetokens);
				args.erase(args.begin()); // procedure name is not an argument
				args.erase(args.end()-1); // eof not an argument either
				global.procedures [proc].prim (args, global, local);



				break;
			}

			case Token::TT_TO: {


				if ((++it)->type != Token::TT_PROCEDURE) {
					throw std::string("TO does not like ") + it->Describe() + " as its first input";
				}
				std::string procname = it->text;
				std::vector<std::string> procargs;
				it++;

				if (global.procedures.find(procname) != global.procedures.end())
					if (global.procedures[procname].primitive)
						throw std::string("TO does not like ") + procname + " as its first input";
					else
						global.procedures.erase(global.procedures.find(procname));


				for (int i = 0; it != sourcetokens.end() && it->type != Token::TT_NL; it++, i++) {
					if (it->type != Token::TT_VARIABLE) {
						throw std::string("TO does not like ") + it->Describe() + " as its " + orderednumber(i+1) + " input";
					}
					procargs.insert(procargs.end(), it->text);
				}

				it++;


				std::string src;
				for (; it != sourcetokens.end() && it->type != Token::TT_END; it++) {
					//proc.sourcetokens.insert(proc.sourcetokens.end(), *it);
					switch (it->type) {
						case Token::TT_NL:
							src += "\n";
							break;
						case Token::TT_WORD:
							src += "\"" + str_replace(str_replace(it->text, "\\", "\\\\"), " ", "\\ ") + " ";
							break;
						case Token::TT_VARIABLE:
							src += ":" + it->text + " ";
							break;
						case Token::TT_EOF:
							throw std::string("TO does not like EOF as its input.");
							break;
						default:
							src += it->text + " ";
					}
				}

				onlogo_printf("%s %s with %d parameters\n", procname.c_str(), global.procedures.find(procname) != global.procedures.end() ? "redefined" : "defined", procargs.size());


				global.procedures[procname] = procedure_t();
				global.procedures[procname].primitive = false;
				global.procedures[procname].procparams = procargs;
				global.procedures[procname].name  = procname;
				global.procedures[procname].proctext = (char*)malloc(src.size()+1);
				global.procedures[procname].argc = procargs.size();
				strcpy(global.procedures[procname].proctext, src.c_str());



				break;
			}
			case Token::TT_NL:
			case Token::TT_EOF:
				break;

			default:

				throw(std::string("Unexpected token: ") + it->Describe() + "\n");
		}
	}




	return Token(Token::TT_UNDEFINED);
}
